/*
 * ViewController.java
 */
package mtvotingserver;

import java.awt.event.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Random;
import javax.swing.*;

/**
 * @author George Lawie 0401739
 * CM3033 Concurrent Programming, Semester 1 2011/12
 * 
 * ViewController
 * 
 * This class is responsible for all interaction and updating of the View class.
 * (This is based on the View/Controller/Model technique.) Here, listeners are
 * added to buttons, and graphical element visibility is also handled. Also,
 * updating of voting progress and values are done via methods here. However,
 * initial values are handled by the View class. A separate InputDialog is
 * used to allow the user to input details of the housemates for this round, but
 * is also handled by this controller.
 * 
 * Further details about methods and variables are contained in-line below.
 */
public class ViewController {

    private View v;
    private ServerModel sm;
    
    /* Constants used throughout the controller. TIMER_DELAY handles (in ms)
     * the frequency of the Timer. NUMBER_OF_HOUSEMATES and NAMES_OF_HOUSEMATES
     * are created empty, but once initialised are kept constant throughout. */
    private int NUMBER_OF_HOUSEMATES;
    private String[] NAMES_OF_HOUSEMATES;
    private int TIMER_DELAY = 500;
    private Timer updateTimer;
    
    /* The multiple graphical elements of View and InputDialog. This allows
     * safe and easy manipulation of the various elements. The names
     * match the elements that they are referring to in the other classes. */
    private JPanel[] hmPanels;
    private JLabel[] hmLabels;
    private JProgressBar[] hmVoteBars;
    private JButton startButton, stopButton, editButton, confirmButton;
    private JTextArea history;
    private JLabel currentLeader, currentLeaderDescription;
    private JTextField[] inputs;
    
    // The jDialog that allows the user to enter housemates' details.
    private ViewInputDialog id;
    
    // The plotter window
    private Plotter plotter = null;
    private int votingRefreshTicks;
    private JDialog plotterDialog = null;
    
    /* This ensures a standard style of time reporting is used throughout
     * the interface. */
    private DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
    
    // storage of voting data from client
    private ClientModel[] clientHistory = null;
    
    // Used by TEST CODE
    Random randomNumGen = new Random(); // TEST CODE
    int totalVotes = 0; // TEST CODE
    
    // Constructor
    public ViewController(View view, ServerModel serverModel) {
        sm = serverModel;
        v = view;

        // get elements from the view to manipulate
        instantiateGraphicalElements();

        // add Listeners to the buttons
        addButtonListeners();
        
        id = new ViewInputDialog(v, false);
        instantiateInputElements();
        
        // launch dialog for entry of housemates' names
        getHousemateDetails();
        
        /* prevent main view from automatically taking focus, allowing dialog
         * window to take focus instead. Then display main GUI. */
        v.setAutoRequestFocus(false);
        v.setVisible(true);
    }

    /* Take the various graphical elements from the GUI that will later be
     * manipulated and add them to the established variables. */
    private void instantiateGraphicalElements() {
        hmPanels = v.getPanels();
        hmLabels = v.getLabels();
        hmVoteBars = v.getVoteBars();
        startButton = v.getStartButton();
        stopButton = v.getStopButton();
        editButton = v.getEditButton();
        history = v.getHistory();
        currentLeader = v.getCurrentLeader();
        currentLeaderDescription = v.getCurrentLeaderDescription();
        
        // Launch the Plotter, and set its parameters
        plotter = new Plotter();
        plotter.init("Time", "Votes", 20);        
        plotterDialog = new JDialog(v, false);
        plotterDialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        plotterDialog.add(plotter);
        plotterDialog.setSize(900,500);
        plotterDialog.setLocation(400,400);
        plotterDialog.setVisible(true);
    }

    /* Opens a round of voting. This disables the Start button and enables the
     * Stop button, and also alerts the ServerModel that voting should
     * commence. A note is passed to the Program History stating that voting
     * has begun.
     * 
     * This method also indirectly starts the Timer object, allowing the
     * constant updating of graphical elements within the GUI. */
    private void startVoting() {
        stopButton.setEnabled(true);
        editButton.setEnabled(false);
        startButton.setEnabled(false);
        currentLeaderDescription.setText("Current Leader");
        sm.setVotingStatus(true);
        votingRefreshTicks = 0;
        plotter.clearValues();
        String startTime = df.format(sm.getVotingStartTime());
        history.append("Voting started at " + startTime + "\n");
        startUpdateTimer(); // begin constant update of variables
    }

    /* Closes voting. This disables the Stop button, and further changes the
     * text across both buttons to a "Voting Finished" notification. The
     * ServerModel is alerted to stop processing votes, and a note is passed to
     * the Program History.
     * 
     * This method also indirectly stops the Timer object. One last update is
     * done on the GUI to ensure it's showing the final data from ServerModel.*/
    private void stopVoting() {
        sm.setVotingStatus(false);
        String stopTime = df.format(sm.getVotingStopTime());
        stopUpdateTimer();
        history.append("Voting stopped at " + stopTime + "\n");
        stopButton.setEnabled(false);
        editButton.setEnabled(true);
        startButton.setEnabled(true);
        startButton.setText("Restart Voting");
        currentLeaderDescription.setText("EVICTED"); 
        updateData(); // a final call to ensure accurate information
        
        String voteLength = df.format(sm.getVotingLength());
        history.append("Voting lasted for " + voteLength + "\n");

        currentLeader.setText(
                currentLeader.getText().toUpperCase(Locale.ENGLISH));
    }
    
    private void editHousemates() {
        editButton.setEnabled(false);
        startButton.setEnabled(false);
        stopButton.setEnabled(false);
        getHousemateDetails();
    }

    // This method adds Action Listeners to the three buttons in the main GUI.
    private void addButtonListeners() {
        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                startVoting();
            }
        });
        stopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                stopVoting();
            }
        });
        editButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                editHousemates();
            }
        });
    }

    /* Starts the Timer thread, using the delay defined by TIMER_DELAY.
     * An ActionListener is addded to it, that causes the data and graphical
     * elements used in the GUI to be updated. */
    private void startUpdateTimer() {
        updateTimer = new Timer(TIMER_DELAY, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateData();
            }
        });
        updateTimer.start(); // start timer
    }
    
    // Stops the Timer.
    private void stopUpdateTimer() {
        updateTimer.stop();
    }

    /* Responsible for updating all graphical elements in the GUI. This includes
     * the maximum value and current value of each progress bar, as well as
     * determining and updating the current leader. This is done after calling
     * getTotalVotes from the ServerModel, and getting each housemates' current
     * votes (via repeated getVotes(name)).
     * 
     * Note: this could occasionally be out of sync with the data as stored in
     * ServerModel, due to the time between getting TotalVotes and individual
     * housemates' votes. For this reason, a final call of this method is
     * necessary after voting finishes, to ensure the most accurate information
     * is displayed. */
    private void updateData() {

        //updateFakeVotes(); // TEST CODE

        int currentTotalVotes = sm.getTotalVotes();
        // single call to ensure all vote bars have same maximum value
        int currentMostVotes = 0;
        int currentMostVoted = 0;
        for (int x = 0; x < NUMBER_OF_HOUSEMATES; x++) {
            hmVoteBars[x].setMaximum(currentTotalVotes);
            int votes = sm.getVotes(NAMES_OF_HOUSEMATES[x]);
            hmVoteBars[x].setValue(votes);
            hmVoteBars[x].setString("" + votes);
            if (currentMostVotes < votes) {
                currentMostVotes = votes;
                currentMostVoted = x;
            }
            
            plotter.pushToTrack(NAMES_OF_HOUSEMATES[x], 
                    votingRefreshTicks, votes);
        } 
        currentLeader.setText(NAMES_OF_HOUSEMATES[currentMostVoted]);
        
        clientHistory = sm.getVotingHistory();
        if (clientHistory != null) {
            for(int x = 0; x < clientHistory.length; x++){
                history.append(clientHistory[x].toString() + "\n");
            }
            clientHistory = null;
        }
        
        // Redraw plotter with new data
        plotterDialog.repaint();
        votingRefreshTicks++;
    }

    /* Launch the ViewInputDialogue for entering and editting housemates'
     * details. As this can only be launched while voting isn't running, and
     * dis/enabling of buttons is handled elsewhere, this literally just
     * handles housemate detail entering.
     * 
     * In order to ensure correct data is stored in the ServerModel, it removes
     * all current housemates, and then re-adds them upon confirmation. */
    private void getHousemateDetails() {
        // display housemate name-entry dialogue
        id.getFirstInput().requestFocusInWindow();
        id.setAutoRequestFocus(true);
        id.setVisible(true);
        
        confirmButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateView();
                id.setVisible(false);
                
            }
        });
    }

    /* Take the various graphical elements from the InputDialoge that will later
     * be needed and add them to the established variables. */
    private void instantiateInputElements() {
        inputs = id.getInputs();
        confirmButton = id.getConfirmButton();
    }
    
    /* This reads the data from the input dialog, and updates the main GUI. This
     * is done be reading inputs from the dialog, passing them to ServerModel,
     * then updating and showing/hiding any necessary panels. */
    private void updateView() {
        // Remove existing details from ServerModel first
        sm.clearOptions();
        
        for (int x = 0; x < 10; x++) {
            if (!inputs[x].getText().isEmpty()) {
                sm.addOption(inputs[x].getText());
            }
        }
        
        /* sending data to the ServerModel and then retrieving it ensures that
         * the View has the exact same data as the ServerModel, at the cost
         * of some efficiency. */
        NAMES_OF_HOUSEMATES = sm.getOptions();
        NUMBER_OF_HOUSEMATES = NAMES_OF_HOUSEMATES.length;
        
        // reset the Plotter
        plotter.clearTracks();
        
        // show and update needed panels, and update plotter tracks
        for (int x = 0; x < NUMBER_OF_HOUSEMATES; x++) {
            hmLabels[x].setText(NAMES_OF_HOUSEMATES[x]);
            hmLabels[x].setToolTipText(NAMES_OF_HOUSEMATES[x]); //for long names
            hmPanels[x].setVisible(true);
            
            plotter.addTrackUnique(NAMES_OF_HOUSEMATES[x], null, 0, 0);
        }
        
        // hide the rest of the panels
        for (int x = NUMBER_OF_HOUSEMATES; x < 10; x++) {
            hmPanels[x].setVisible(false);
        }
        
        startButton.setEnabled(true);
        editButton.setEnabled(true);
    }

    //*** TEST CODE
    private void updateFakeVotes() {
        for (int x = 0; x < NUMBER_OF_HOUSEMATES; x++) {
            int randomVa = randomNumGen.nextInt(30);
            int randomVb = randomNumGen.nextInt(30);
            hmVoteBars[x].setValue(hmVoteBars[x].getValue() 
                    + randomVa + randomVb);
            totalVotes = totalVotes + randomVa + randomVb;
            hmVoteBars[x].setString(hmVoteBars[x].getValue() + " votes");
        }
    }

    //*** TEST CODE
    private int getFakeVotes(String name) {
        // TODO replace calls to this with sm.getVotes(String name)

        int currentVote = 0;

        for (int x = 0; x < NUMBER_OF_HOUSEMATES; x++) {
            if (name.equals(NAMES_OF_HOUSEMATES[x])) {
                currentVote = hmVoteBars[x].getValue();
                break; // stop searching
            }
        }
        return currentVote;
    }

    //*** TEST CODE
    private int getFakeTotalVotes() {
        // TODO replace calls to this with sm.getTotalVotes()
        return totalVotes;
    }
    //*** TEST CODE

   
}
